package com.syl.starter.itools.framework.utils;

import com.syl.starter.itools.common.utils.StringUtils;
import lombok.NoArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.GenericWebApplicationContext;

import java.lang.reflect.Field;

/**
 * 想要使用必须把该类加入项目配置中 例: SpringConfig
 * 普通类中获取spring容器中的bean对象
 * 注意：在 InitializingBean 方法中无法获取相应的bean 此时beanFactory 还未初始化,无法获取到上下文
 *
 * @author syl
 * @create 2018-11-16 15:23
 **/
@Component
@NoArgsConstructor
public class SpringUtils implements ApplicationContextAware {
    private static final Logger LOG = LoggerFactory.getLogger(SpringUtils.class);

    /**
     * spring 基础环境上下文
     */
    public static ApplicationContext applicationContext;
    /**
     * spring 注解注册bean容器
     */
    public static GenericWebApplicationContext genericWebApplicationContext;

    private static final String PRODUCTION = "pro";
    public static final String H = "h";
    public static final String ADVISED = "advised";
    /**
     * spring cloud 项目环境变量
     */
    private static final String ENVIRONMENT = "spring.cloud.config.profile";
    /**
     * 当前环境变量
     */
    private static final String SPRING_PROFILES_ACTIVE = "spring.profiles.active";

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        LOG.debug("开始初始化 spring 上下文");
        if (SpringUtils.applicationContext == null) {
            SpringUtils.applicationContext = applicationContext;
            if (applicationContext instanceof GenericWebApplicationContext)
                SpringUtils.genericWebApplicationContext = (GenericWebApplicationContext) applicationContext;
        }
    }

    /**
     * 获取当前项目是否正式环境
     * @return
     */
    public static boolean isPropEnvironment(){
        return StringUtils.equals(getProperty(SPRING_PROFILES_ACTIVE), PRODUCTION);
    }

    /**
     * 获取当前环境
     * @return
     */
    public static String getActiveEnvironment(){
        return getProperty(SPRING_PROFILES_ACTIVE);
    }

    /**
     * 辅助获取spring 参数
     * @return
     */
    public static String getProperty(String key){
        Environment environment = applicationContext.getEnvironment();
        return environment.getProperty(key);
    }

    /**
     * 通过名称获取bean
     * @param name
     * @return
     */
    public static Object getBean(String name){
        return getBean(name, Object.class);
    }

    /**
     * 通过名称获取bean
     * @param name
     * @param cz 获取目标类型
     *
     * @return
     */
    public static <T> T getBean(String name, Class<T> cz){
        ApplicationContext context = SpringUtils.applicationContext;
        if(context == null)
            return null;
        Object bean = context.getBean(name);
        return cz.cast(bean);
    }

    /**
     * 通过class获取Bean
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz){
        ApplicationContext context = SpringUtils.applicationContext;
        if(context == null)
            return null;
        return context.getBean(clazz);
    }


    /**
     * 获取目标父亲对象的指定私有字段
     *
     * @param target 目标代理对象
     * @param field 目标私有字段
     * @return 获取到的Field
     * @throws Exception
     */
    public static Field getSuperClassPrivateField(Object target, String field) throws Exception {
        Field declaredField = target.getClass().getSuperclass().getDeclaredField(field);
        declaredField.setAccessible(true);
        return declaredField;
    }

    /**
     * 获取目标父亲对象的指定私有字段对象
     * @param target 目标代理对象
     * @param field  目标私有字段
     * @param tClass 目标字段类型
     * @return
     * @throws Exception
     */
    public static <T> T getSuperClassPrivateField(Object target, String field, Class<T> tClass) throws Exception {
        Field tempField = getSuperClassPrivateField(target, field);
        return tClass.cast(tempField.get(target));
    }

    /**
     *  获取目标类的指定私有字段对象
     * @param target 目标类
     * @param field 目标私有字段
     * @return
     * @throws Exception
     */
    public static Field getPrivateField(Object target, String field) throws Exception {
        Field declaredField = target.getClass().getDeclaredField(field);
        declaredField.setAccessible(true);
        return declaredField;
    }

    /**
     *
     * @param target 目标类
     * @param field 目标私有字段
     * @param tClass  目标字段类型
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> T getPrivateField(Object target, String field, Class<T> tClass) throws Exception {
        Field privateField = getPrivateField(target, field);
        return tClass.cast(privateField.get(target));
    }

    /**
     * 获取aop代理对象的真实对象
     * @param target
     * @return
     * @throws Exception
     */
    public static Object getAopProxyRealObject(Object target) throws Exception {
        AopProxy aopProxy = getSuperClassPrivateField(target, H, AopProxy.class);
        AdvisedSupport advised = getPrivateField(aopProxy, ADVISED, AdvisedSupport.class);
        return advised.getTargetSource().getTarget();
    }

    /**
     * 注入bean到spring 上下文中
     * @param beanName bean名称
     * @param object bean对象
     * @param cz bean对象类型
     */
    public static void injectBeanHelp(String beanName, Object object, Class<?> cz){
//        Assert.notNull(genericWebApplicationContext, "请先对SpringUtils对象进行注入");
        if (genericWebApplicationContext == null) return;
        if (genericWebApplicationContext.containsBeanDefinition(beanName)){
            //如果Spring上下文已存在Bean，先删除
            genericWebApplicationContext.removeBeanDefinition(beanName);
        } else{
            //如果不存在，创建通用类
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(cz);
            genericWebApplicationContext.registerBeanDefinition(beanName, builder.getBeanDefinition());
        }
//        //注入JdbcTemplate实例
        genericWebApplicationContext.getBeanFactory().registerSingleton(beanName, object);
    }

}
